package com.starmark.gateway.springcloud.provider.filter;


import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.starmark.gateway.springcloud.provider.config.IgnoreUrlConfig;
import com.starmark.gateway.springcloud.provider.constants.GatewayConstant;
import com.starmark.gateway.springcloud.provider.dto.GatewayProjectDto;
import com.starmark.gateway.springcloud.provider.dto.SecurityUser;
import com.starmark.gateway.springcloud.provider.response.ErrorResponseData;
import com.starmark.gateway.springcloud.provider.service.IAuthService;
import com.starmark.gateway.springcloud.provider.service.IJwtTokenService;
import com.starmark.gateway.springcloud.provider.service.IRouteService;
import com.starmark.gateway.springcloud.provider.utils.JwtHelper;
import com.starmark.gateway.springcloud.provider.utils.RequestHelper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * 请求url权限校验
 */

@Component
@Slf4j
public class AccessGatewayFilter implements GlobalFilter, Ordered {


    @Autowired
    private IAuthService authService;

    @Autowired
    private IJwtTokenService jwtTokenService;

    @Autowired
    private IRouteService routeService;
    @Autowired
    private IgnoreUrlConfig ignoreUrlConfig;
    @Autowired
    private JwtHelper jwtHelper;

    /**
     * 1.首先网关检查token是否有效，无效直接返回401，不调用签权服务
     * 2.调用签权服务器看是否对该请求有权限，有权限进入下一个filter，没有权限返回401
     *
     * @param exchange
     * @param chain
     * @return
     */
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder builder = request.mutate();
        String method = request.getMethodValue();
        String url = request.getPath().value();
        if (ignoreUrlConfig.isIgnoreUrl(url)) {

            //如果发现是css或者js文件，直接放行
            return chain.filter(exchange);
        }
        log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
        String projectCode = RequestHelper.getRequestProjectCode(request);
        Assert.hasText(projectCode, "工程ID不存在");
        GatewayProjectDto gatewayProjectDto = routeService.getGatewayProject(projectCode);
        Assert.notNull(gatewayProjectDto, "工程不存在:" + projectCode);
        exchange.getAttributes().put(GatewayConstant.X_CLIENT_PROJECT_CODE, projectCode);

        String authentication = request.getHeaders().getFirst(GatewayConstant.X_CLIENT_TOKEN_USER);
        SecurityUser securityUser = null;
        String userToken = "";
        //访问匿名网址时,如果有登陆帐号,也继续携带
        if (!StrUtil.isEmpty(authentication)) {
            userToken = jwtTokenService.getUserInfoByJwtToken(authentication);
            securityUser = JSONObject.parseObject(userToken, SecurityUser.class);
            if (!Objects.equals(securityUser.getProjectCode(), projectCode)) {
                throw new RuntimeException("非法的token访问非法的URL");
            }
            exchange.getAttributes().put(GatewayConstant.X_CLIENT_TOKEN_USER_ID, securityUser.getId());
            //为信息安全及解决乱码问题,传递加密后的用户信息
            builder.header(GatewayConstant.X_CLIENT_TOKEN, jwtHelper.sign(JSONObject.toJSONString(securityUser), "gateway", 12000L));
        }


        //不需要网关签权的url
        if (authService.ignoreAuthentication(url, projectCode)) {
            return chain.filter(exchange);
        }


        // 如果请求未携带token信息, 直接跳出
        if (StrUtil.isEmpty(authentication) || jwtTokenService.isTokenExpired(authentication)) {
            log.debug("url:{},method:{},headers:{}, 请求未携带token信息", url, method, request.getHeaders());
            return unLogin(exchange, gatewayProjectDto);
        }

        if (authService.hasPermission(securityUser, url, method)) {
            //token续期
            if (jwtTokenService.shouldTokenRefresh(authentication, projectCode)) {
                String newToken = jwtTokenService.createJwtToken(userToken, projectCode);
                exchange.getResponse().getHeaders().add("x-auth-token", newToken);
                exchange.getResponse().getHeaders().add("Access-Control-Expose-Headers", "*");
            }
            return chain.filter(exchange.mutate().request(builder.build()).build());
        } else {
            return unauthorized(exchange);
        }


    }


    /**
     * 没有登陆,跳到登陆页面
     *
     * @param serverWebExchange 交换系统
     * @return 没有登陆的JSON
     */
    private Mono<Void> unLogin(ServerWebExchange serverWebExchange, GatewayProjectDto gatewayProjectDto) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);

        DataBuffer buffer = serverWebExchange.getResponse()
                .bufferFactory().wrap(JSONObject.toJSONString(ErrorResponseData.newInstance(HttpStatus.UNAUTHORIZED.value(), "未登陆", gatewayProjectDto.getLoginAddress())).getBytes(StandardCharsets.UTF_8));
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }


    /**
     * 网关拒绝，返回401
     *
     * @param serverWebExchange 请求数据
     * @return 返回401
     */
    private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

        DataBuffer buffer = serverWebExchange.getResponse()
                .bufferFactory().wrap(JSONObject.toJSONString(ErrorResponseData.newInstance(HttpStatus.UNAUTHORIZED.value(), "无权限")).getBytes(StandardCharsets.UTF_8));
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return -21;
    }




}
